package com.lenovo.spider;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.yaml.snakeyaml.Yaml;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:393803588@qq.com">HanL(liuhan3)</a>
 * @date 17-12-18
 */
public class Main {

    private static Pattern pattern = Pattern.compile("^(?<locatorType>xpath|css):(?<locator>.+)");

    public static InputStream getResource(String filePath) {
        return ClassLoader.getSystemResourceAsStream(filePath);
    }

    /**
     * 读取yml类型的模板文件
     *
     * @param yamlFile 文件路径
     * @return json
     */
    public static JSONObject readYaml(String yamlFile) {
        InputStream in = getResource(yamlFile);
        Yaml yaml = new Yaml();
        return yaml.loadAs(in, JSONObject.class);
    }

    /**
     * 读取yml类型的模板文件
     *
     * @param yamlText yaml格式文本
     * @return json
     */
    public static JSONObject readYamlText(String yamlText) {
        Yaml yaml = new Yaml();
        return yaml.loadAs(yamlText, JSONObject.class);
    }

    /**
     * 模拟抓取网页
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static Html fetchHtml(String url) throws IOException {
        return new Html(Jsoup.parse(new File(url), "UTF-8"));
    }

    public static JSONObject doExtract(JSONObject template, Html html) {
        LocatorExtractor extractor = new LocatorExtractor(html);
        JSONObject result = (JSONObject) template.clone();
        JSONPath.paths(template).entrySet().stream()
                .filter(entity -> entity.getValue() instanceof String)
                .filter(entity -> pattern.matcher((CharSequence) entity.getValue()).matches())
                .forEach(entity -> JSONPath.set(result, entity.getKey(), extractor.read((String) entity.getValue()).get()));
        return result;
    }

    /**
     * 使用yml魔板解析网页demo
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        // 读取模板内容
        JSONObject template = readYaml("Site1.yml");
        // 读取网页内容
        Html html = fetchHtml("/home/hanl/Workspace/IdeaProjects/SpiderTemplateEngine/src/main/resources/test.html");
        // 开始解析
        perfInvoke(() -> System.out.println(doExtract(template, html)));
    }


    private static void perfInvoke(Func func) throws IOException {
        LocalDateTime start = LocalDateTime.now();
        func.invoke();
        LocalDateTime end = LocalDateTime.now();
        System.out.printf("Cost: %d ms", Duration.between(start, end).toMillis());
    }

    interface Func {
        /**
         * 定义一个不需要参数，返回为空的函数体
         *
         * @throws IOException
         */
        void invoke() throws IOException;
    }

    static class LocatorExtractor {
        public static final String LOCATOR_TYPE = "locatorType";
        public static final String LOCATOR = "locator";

        private Html html;

        public LocatorExtractor(Html html) {
            this.html = html;
        }

        public LocatorExtractor(Document document) {
            this.html = new Html(document);
        }

        public Selectable read(String locator) {
            Matcher matcher = pattern.matcher(locator);
            if (!matcher.matches()) {
                return null;
            }
            Optional<LocatorType> locatorType = LocatorType.of(matcher.group(LOCATOR_TYPE));
            if (!locatorType.isPresent()) {
                return null;
            }

            return locatorType.get().select(html, matcher.group(LOCATOR));
        }


    }

    enum LocatorType {
        XPATH {
            @Override
            Selectable select(Selectable selectable, String locator) {
                return selectable.xpath(locator);
            }
        },
        CSS {
            @Override
            Selectable select(Selectable selectable, String locator) {
                return selectable.css(locator);
            }
        },
        REGEXP {
            @Override
            Selectable select(Selectable selectable, String locator) {
                return selectable.regex(locator);
            }
        },
        JSONPATH {
            @Override
            Selectable select(Selectable selectable, String locator) {
                return selectable.jsonPath(locator);
            }
        };


        public static Optional<LocatorType> of(String value) {
            if (XPATH.name().equalsIgnoreCase(value)) {
                return Optional.of(XPATH);
            } else if (CSS.name().equalsIgnoreCase(value)) {
                return Optional.of(CSS);
            } else {
                return Optional.empty();
            }
        }

        /**
         * 查询节点
         *
         * @param selectable 可查询节点
         * @param locator    位置限定语句
         * @return
         */
        abstract Selectable select(Selectable selectable, String locator);
    }
}
